package me.kevinwalker.core.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import me.kevinwalker.core.annotation.RecordField;
import org.bson.Document;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class AnnotationClassUtil {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Converts objects into key-value pairs in a mapping table in a HashMap
     * 将对象转换为键值对存储在HashMap中的映射表
     *
     * @param obj 需要转换的对象
     * @return 包含对象字段和对应值的HashMap映射表
     */
    public static Map<String, Object> parseObjectToMap(Object obj) {
        HashMap<String, Object> returnHashMap = new HashMap<>();
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        try {
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                if (declaredField.isAnnotationPresent(RecordField.class)) {
                    String value = declaredField.getAnnotation(RecordField.class).value();
                    returnHashMap.put(value, declaredField.get(obj));
                } else {
                    returnHashMap.put(declaredField.getName(), declaredField.get(obj));
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return returnHashMap;
    }


    /**
     * Convert a Document object to a dao object
     * 将Document对象转换为dao对象
     *
     * @param document 待转换的Document对象
     * @param tClass   待转换的目标类
     * @param <T>      目标类的类型
     * @return 转换后的Dao对象
     */
    public static <T> T documentToDao(Document document, Class<T> tClass) {
        try {
            T o = tClass.getConstructor().newInstance();
            Field[] declaredFields = tClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                if (document.containsKey(declaredField.getName())) {
                    Object fieldValue = document.get(declaredField.getName());
                    if (fieldValue instanceof Document) {
                        Document documentTemp = (Document) fieldValue;
                        String json = documentTemp.toJson();
                        declaredField.set(o, objectMapper.readValue(json, declaredField.getType()));
                    } else if (declaredField.isAnnotationPresent(RecordField.class)) {
                        String value = declaredField.getAnnotation(RecordField.class).value();
                        declaredField.set(o, document.get(value) != null ? document.get(value).toString() : null);
                    } else {
                        declaredField.set(o, fieldValue);
                    }
                }
            }
            return o;
        } catch (JsonProcessingException | InvocationTargetException | InstantiationException | IllegalAccessException |
                 NoSuchMethodException e) {
            throw new RuntimeException("Error converting Document to Dao", e);
        }
    }

    /**
     * Gets a list of classes that implement a particular annotation interface
     * 获取实现特定注解接口的类列表
     *
     * @param classList       需要判断的列表类
     * @param annotationClass 注解类
     * @return 实现注解类的列表类
     */
    public static List<Class<?>> getClassesImplementingInterface(List<Class<?>> classList, Class<? extends Annotation> annotationClass) {
        if (annotationClass == null || !annotationClass.isAnnotation()) {
            throw new IllegalArgumentException("The provided class must be an annotation interface");
        }

        List<Class<?>> implementingClasses = new ArrayList<>();
        for (Class<?> clazz : classList) {
            // Determines if it is the same annotation and does not itself join the returned collection
            if (clazz.isAnnotationPresent(annotationClass) && (!clazz.equals(annotationClass))) {
                implementingClasses.add(clazz);

            }
        }
        return implementingClasses;
    }


    /**
     * Gets all the class names of the package that a class in without iteration
     * 取得某一类所在包的所有类名 不含迭代
     *
     * @param classLocation 类路径
     * @param packageName   包名
     * @return 类名数组
     */
    public static String[] getPackageAllClassName(String classLocation, String packageName) {
        // 将packageName分解
        String[] packagePathSplit = packageName.split("[.]");
        StringBuilder realClassLocation = new StringBuilder(classLocation);
        for (String s : packagePathSplit) {
            realClassLocation.append(File.separator).append(s);
        }

        File packageDir = new File(realClassLocation.toString());
        if (packageDir.isDirectory()) {
            return packageDir.list();
        }
        return new String[0];
    }

    /**
     * Gets all the classes from the package
     * 从包中获取所有的类
     *
     * @param packageName 包名
     * @return Class列表
     */
    public static List<Class<?>> getClasses(String packageName) {

        // 定义一个class类的集合
        List<Class<?>> classes = new ArrayList<>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    JarFile jar;
                    jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jar.entries();
                    // 循环迭代
                    while (entries.hasMoreElements()) {
                        // 获取jar里的一个实体，可以是目录和一些jar包里的其他文件，如META-INF等文件
                        JarEntry entry = entries.nextElement();
                        packageName = getJarFileName(packageName, entry, packageDirName, recursive, classes);

                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * Obtain the file name if the file type is jar package
     * 文件类型为jar包时获取文件名。
     *
     * @param packageName    包名
     * @param entry          jar实体
     * @param packageDirName 包目录名
     * @param recursive      是否递归
     * @param classes        类的列表
     * @return jar文件名
     */
    private static String getJarFileName(String packageName, JarEntry entry, String packageDirName, boolean recursive, List<Class<?>> classes) {
        String name = entry.getName();
        // 如果是以/开头的
        if (name.charAt(0) == '/') {
            // 获取后面的字符串
            name = name.substring(1);
        }
        // 如果前半部分和定义的包名相同
        if (name.startsWith(packageDirName)) {
            int idx = name.lastIndexOf('/');
            // 如果以"/"结尾是一个包
            if (idx != -1) {
                // 获取包名，把"/"替换成"."
                packageName = name.substring(0, idx).replace('/', '.');
            }
            // 如果可以迭代下去，并且是一个包
            if ((idx != -1) || recursive && (name.endsWith(".class") && !entry.isDirectory())) {
                // 去掉后面的".class"，获取真正的类名
                String className = name.substring(packageName.length() + 1, name.length() - 6);
                try {
                    // 添加到classes
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException | NoClassDefFoundError e) {
                    e.printStackTrace();
                }
            }
        }
        return name;
    }


    /**
     * Get all the classes in the package as a file
     * 以文件的形式获取包下的所有类
     *
     * @param packageName 包名
     * @param packagePath 包路径
     * @param recursive   是否递归
     * @param classes     存储类的集合
     */
    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) {
        // 获取此包的目录
        File dir = new File(packagePath);
        // 如果目录不存在或不是目录，则直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        //如果存在 就获取包下的所有文件 包括目录
        //自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
        File[] dirFiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));

        if (dirFiles == null) {
            return;
        }
        // 循环所有文件
        for (File file : dirFiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(),
                        file.getAbsolutePath(),
                        recursive,
                        classes);
            } else {
                String className = file.getName().substring(0, file.getName().lastIndexOf(".class"));
                try {
                    // 添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException | NoClassDefFoundError e) {
                    e.printStackTrace();
                }
            }
        }
    }
}